#include "net/TcpClient.h"
#include "net/TcpConnection.h"
#include "net/EventLoop.h"
#include "Base/Logger.h"

namespace toym
{
namespace net
{

std::atomic_uint64_t TcpClient::s_connSeq(0);

TcpClient::TcpClient(EventLoop* loop, const InetAddress& serverAddr, const std::string& clientName)
    : m_loop(loop),
      m_clientName(clientName),
      m_connector(loop, serverAddr),
      m_conn(nullptr),
      m_serverAddr(serverAddr)
{
    m_connector.setConnectedCallback(
        [this](int sockfd) {
            this->connectedCallback(sockfd);
        }
    );
    LOG_DEBUG << "TcpClient::TcpClient [" << this << "]";
}

TcpClient::~TcpClient() = default;

void TcpClient::disconnect() {
    m_loop->runInLoop([this]{
        this->disconnectInLoop();
    });
}

void TcpClient::disconnectInLoop() {
    m_loop->assertInLoopThread();
    LOG_DEBUG << "TcpClient::disconnectInLoop";
    m_conn->shutdown();
}

void TcpClient::connectedCallback(int sockfd) {
    LOG_DEBUG << "TcpClient::connectedCallback sockfd=" << sockfd;
    m_loop->assertInLoopThread();
    char buf[48];
    ++s_connSeq;
    snprintf(buf, sizeof(buf), ":%s#%lu", m_serverAddr.toIpPort() ,s_connSeq.load());
    std::string connName = m_clientName + buf;

    Socket sock(sockfd);
    InetAddress localAddr = sock.getLocalAddr();
    m_conn.reset(new TcpConnection(std::move(sock), m_loop, localAddr,m_serverAddr));
    m_conn->setCloseCb([this](TcpConnectionPtr conn) {
        this->removeConnection(conn);
    });

    if (m_conntionCb) {
        m_conn->setConnectionCb(m_conntionCb);
    }else {
        m_conn->setConnectionCb(TcpClient::defaultConnectionCallback);
    }

    if (m_messageCb) {
        m_conn->setMessageCb(m_messageCb);
    }else {
        m_conn->setMessageCb(TcpClient::defaultMessageCallback);
    }

    if (m_writeComplateCb) {
        m_conn->setWriteComplateCb(m_writeComplateCb);
    }

    LOG_INFO << "new connection: " << connName;
    m_conn->setConnName(std::move(connName));

    m_conn->connectionEstablished();
}

void TcpClient::removeConnection(TcpConnectionPtr conn) {
    LOG_DEBUG << "TcpClient::removeConnection " << conn->getConnName();
    m_loop->runInLoop(
        [conn, this]() {
            this->removeConnectionInLoop(conn);
        }
    );
}

void TcpClient::removeConnectionInLoop(TcpConnectionPtr conn) {
    LOG_DEBUG << "TcpClient::removeConnectionInLoop " << conn->getConnName() ;
    m_loop->assertInLoopThread();
    m_conn = nullptr;
    m_loop->enqueueLoop(
        [conn]() {
            conn->connectionDestroy();
        }
    );
}

void TcpClient::defaultConnectionCallback(const TcpConnectionPtr& conn) {
    if (conn->connected()) {
        LOG_INFO << "new connection from " << conn->getPeerAddr().toIpPort();
    } else {
        LOG_INFO << "disconnect from " << conn->getPeerAddr().toIpPort();
    }
}

void TcpClient::defaultMessageCallback(const TcpConnectionPtr& conn, Buffer* buf, Timestamp ts) {
    LOG_INFO << "recv msg: " << buf->readAsString() 
             << ", form " << conn->getPeerAddr().toIpPort()
             << ", at " << ts.toFormatString();
}


} // namespace net

} // namespace toym
